#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <netinet/ip_icmp.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PACKET_SIZE 64
#define PING_TIMEOUT 2

class IcmpPing {
public:
    IcmpPing() {
        m_sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
        if (m_sockfd < 0) {
            std::cerr << "Error creating socket" << std::endl;
            exit(1);
        }
    }

    ~IcmpPing() {
        close(m_sockfd);
    }

    void sendPing(const char* target) {
        struct sockaddr_in targetAddr;
        targetAddr.sin_family = AF_INET;
        inet_pton(AF_INET, target, &targetAddr.sin_addr);

        struct icmp packet;
        packet.icmp_type = ICMP_ECHO;
        packet.icmp_code = 0;
        packet.icmp_id = getpid();
        packet.icmp_seq = 0;
        packet.icmp_cksum = 0;
        packet.icmp_cksum = checksum((uint16_t*)&packet, sizeof(packet));

        int bytesSent = sendto(m_sockfd, &packet, sizeof(packet), 0, (struct sockaddr*)&targetAddr, sizeof(targetAddr));
        if (bytesSent < 0) {
            std::cerr << "Error sending ICMP packet" << std::endl;
            exit(1);
        }
    }

    void receivePing() {
        char buffer[PACKET_SIZE];
        struct sockaddr_in fromAddr;
        socklen_t fromAddrLen = sizeof(fromAddr);

        int bytesRead = recvfrom(m_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&fromAddr, &fromAddrLen);
        if (bytesRead < 0) {
            std::cerr << "Error receiving ICMP packet" << std::endl;
            exit(1);
        }

        std::cout << "Received " << bytesRead << " bytes from " << inet_ntoa(fromAddr.sin_addr) << std::endl;
    }

private:
    int m_sockfd;

    uint16_t checksum(uint16_t* buffer, int size) {
        uint32_t checksum = 0;
        while (size > 1) {
            checksum += *buffer++;
            size -= sizeof(uint16_t);
        }
        if (size) {
            checksum += *(uint8_t*)buffer;
        }
        checksum = (checksum >> 16) + (checksum & 0xffff);
        checksum += (checksum >> 16);
        return (uint16_t)(~checksum);
    }
};

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <target_ip>" << std::endl;
        return 1;
    }

    const char* target = argv[1];
    IcmpPing ping;

    ping.sendPing(target);
    ping.receivePing();

    return 0;
}
